package forum.server.persistence;

import java.security.NoSuchAlgorithmException;
import java.util.List;
import java.util.Vector;
import org.hibernate.HibernateException;
import org.hibernate.Session;
import forum.server.controller.MyLogger;
import forum.server.controller.Security;
import forum.server.domain.Admin;
import forum.server.domain.Guest;
import forum.server.domain.LoggedInUser;
import forum.server.domain.Moderator;
import forum.server.domain.Post;
import forum.server.domain.PostContent;
import forum.shared.exceptions.DatabaseException;
import forum.shared.exceptions.NoSuchPostException;
import forum.shared.exceptions.NoSuchUserException;


/*
 * The hibernate package saves inherited objects by putting all the
 * common fields in the same table - the table of the Meta class,
 * in this case there is one table - Users and Tables Administrators and
 * Moderators just have a link to the Users table.
 * Since Admin inherits from Moderator, then each Admin is also stored in the Moderators table.      
 */


public class HibernateSqlDataManipulator implements DataManipulatorInt {

	@SuppressWarnings("unchecked")
	public HibernateSqlDataManipulator() throws DatabaseException{
		Guest g = null;
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Guest> guests=(List<Guest>)session.createQuery("from Guest").list();      
			if(!(guests==null ||guests.size()==0 || guests.get(0)==null)) g=guests.get(0);
			session.getTransaction().commit();


			if(g==null) {//if the table is still empty.
				g = new Guest();
				session = HibernateUtil.getSessionFactory().getCurrentSession();
				session.beginTransaction();
				session.save(g);
				session.getTransaction().commit();
			}
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: init the guests count failed");
			throw new DatabaseException("Data Base: init the guests count failed");
		}
	}

	@Override
	public int addPost(Post p) throws DatabaseException {
		return addReply(-1,p);
	}


	@Override
	public int addReply(int fatherId, Post reply) throws DatabaseException {
		try{
			reply.setParentId(fatherId);
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			session.save(reply);
			int res = reply.getPostId();
			session.getTransaction().commit();
			return res;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: adding post failed");
			throw new DatabaseException("Data Base: adding post failed");
		}
	}


	@Override
	public void addUser(LoggedInUser user) throws DatabaseException {
		if(isUserExists(user.getUsername())){
			throw new DatabaseException("Username already exists, choose another");
		}
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			session.save(user);
			session.getTransaction().commit();      
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: adding user "+ user.getUsername() +" failed");
			throw new DatabaseException("Data Base: adding user "+ user.getUsername() +" failed");
		}

	}


	@Override
	public int countUsers() throws DatabaseException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			Long count=new Long(0);
			count+=((Long)session.createQuery("select count(UserName) from LoggedInUser").iterate().next()).longValue();
			session.getTransaction().commit();      
			return count.intValue();
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: counting users failed");
			throw new DatabaseException("Data Base: counting users failed");
		}
	}


	@Override
	public void deletePost(int postId) throws DatabaseException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			session.createQuery("delete from Post where postId="+postId).executeUpdate();
			session.createQuery("delete from Post where parentId="+postId).executeUpdate();
			session.getTransaction().commit();
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: deleting post: "+postId+" failed");
			throw new DatabaseException("Data Base: deleting post: "+postId+" failed");
		}
	}


	@SuppressWarnings("unchecked")
	@Override
	public void deleteUser(String username) throws DatabaseException,NoSuchUserException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<LoggedInUser> lst=(List<LoggedInUser>)session.createQuery("select user from LoggedInUser user where user.username='"+username+"'").list();
			if(lst==null ||lst.size()==0 || lst.get(0)==null)
				throw new NoSuchUserException("Data Base: deleting user: "+username+" failed - no such user.");
			//since moderator inherits from LoggedInUser and Admin inherits from Moderator
			//then delete from LoggedInUser also deletes from Admin and Moderator.
			session.createQuery("delete from LoggedInUser where username='"+username+"'").executeUpdate();
			session.getTransaction().commit();
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: deleting user: "+username+" failed");
			throw new DatabaseException("Data Base: deleting user: "+username+" failed");
		}
	}


	@SuppressWarnings("unchecked")
	@Override
	public LoggedInUser demoteModerator(String username)
	throws DatabaseException, NoSuchUserException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<LoggedInUser> lst=(List<LoggedInUser>)session.createQuery("select user from LoggedInUser user where user.username='"+username+"'").list();
			if(lst==null ||lst.size()==0 || lst.get(0)==null){
				session.getTransaction().commit();
				throw new NoSuchUserException("Data Base: demoting user: "+username+" failed - no such user.");
			}

			Moderator mod=(Moderator)session.createQuery("select user from Moderator user where user.username='"+username+"'").iterate().next();
			session.createQuery("delete from Moderator where username='"+username+"'").executeUpdate();
			session.getTransaction().commit();
			session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			LoggedInUser res=new LoggedInUser(mod);
			session.save(res);
			session.getTransaction().commit();
			return res;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: demoting user: "+username+" failed");
			throw new DatabaseException("Data Base: demoting user: "+username+" failed");
		}
	}

	/**
	 * deprecated method due to race conditions between the 2 servers - desktop & web.
	 */
	@Override
	@Deprecated
	public int generateNewPostId(){
		return 0;
	}



	@SuppressWarnings("unchecked")
	@Override
	public Vector<Moderator> getAllModerators() throws DatabaseException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Moderator> lst=(List<Moderator>)session.createQuery("from Moderator").list();
			List<Admin> admins=(List<Admin>)session.createQuery("from Admin").list();
			Vector<Moderator> res=new Vector<Moderator>();
			for(Moderator m:lst)
				if(!admins.contains(m)) res.add(m);
			session.getTransaction().commit();
			return res;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: retrieving all the moderators failed");
			throw new DatabaseException("Data Base: retrieving all the moderators failed");
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public Vector<LoggedInUser> getAllUsers() throws DatabaseException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<LoggedInUser> lst=(List<LoggedInUser>)session.createQuery("from LoggedInUser").list();
			Vector<LoggedInUser> res=new Vector<LoggedInUser>();
			for(LoggedInUser m:lst)
				res.add(m);
			session.getTransaction().commit();
			return res;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: getting all the users failed");
			throw new DatabaseException("Data Base: getting all the users failed");
		}
	}


	@SuppressWarnings("unchecked")
	@Override
	public Vector<LoggedInUser> getAllUsersForPromotion()
	throws DatabaseException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Moderator> lst=(List<Moderator>)session.createQuery("from Moderator").list();
			//any admins will appear also in the moderators table.
			List<LoggedInUser> logged=(List<LoggedInUser>)session.createQuery("from LoggedInUser").list();
			Vector<LoggedInUser> res=new Vector<LoggedInUser>();
			for(LoggedInUser l:logged)
				if(!lst.contains(l)) res.add(l);
			session.getTransaction().commit();
			return res;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: retrieving all the users for promotion failed");
			throw new DatabaseException("Data Base: retrieving all the users for promotion failed");
		}
	}


	@SuppressWarnings("unchecked")
	@Override
	public Post getPost(int postId) throws DatabaseException,
	NoSuchPostException {
		try{
			Post res=null;
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Post> posts=(List<Post>)session.createQuery("from Post where postId="+postId).list();      
			if(!(posts==null ||posts.size()==0 || posts.get(0)==null)) res=posts.get(0);  
			session.getTransaction().commit();
			if(res==null) {
				MyLogger.getInstance().logInfo("Data Base: Post not found.");
				throw new NoSuchPostException("Data Base: Post not found.");
			}
			return res;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: retrieving post "+postId+" failed");
			throw new DatabaseException("Data Base: retrieving post "+postId+" failed");
		}
	}


	@SuppressWarnings("unchecked")
	@Override
	public Vector<Post> getPosts() throws DatabaseException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Post> posts=(List<Post>)session.createQuery("from Post where parentId=-1").list();        
			session.getTransaction().commit();
			Vector<Post> res=new Vector<Post>();
			for(Post p:posts)
				res.add(p);
			return res;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: getting all the posts failed");
			throw new DatabaseException("Data Base: getting all the posts failed");
		}
	}


	@SuppressWarnings("unchecked")
	@Override
	public Vector<Post> getReplies(int postId) throws DatabaseException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Post> posts=(List<Post>)session.createQuery("select post from Post post where post.parentId="+postId).list();
			session.getTransaction().commit();
			Vector<Post> res=new Vector<Post>();
			for(Post p:posts)
				res.add(p);
			return res;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: retrieving replies for post "+postId+" failed");
			throw new DatabaseException("Data Base: retrieving replies for post "+postId+" failed");
		}
	}


	@SuppressWarnings("unchecked")
	@Override
	public LoggedInUser getUser(String username) throws DatabaseException,
	NoSuchUserException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<LoggedInUser> lst=(List<LoggedInUser>)session.createQuery("select user from LoggedInUser user where user.username='"+username+"'").list();
			LoggedInUser res=null;
			if(!(lst==null ||lst.size()==0 || lst.get(0)==null)) res=lst.get(0);
			session.getTransaction().commit();
			if(res==null) {
				MyLogger.getInstance().logInfo("Data Base: user"+username+" not found.");
				throw new NoSuchUserException("Data Base: user"+username+" not found.");
			}
			return res;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: retrieving user: "+username+" failed");
			throw new DatabaseException("Data Base: retrieving user: "+username+" failed");
		}
	}


	@Override
	public boolean isUserExists(String user) throws DatabaseException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			int size=session.createQuery("select username from LoggedInUser where username='"+user+"'").list().size();      
			session.getTransaction().commit();
			return size!=0;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: query is user exists "+user+" failed");
			throw new DatabaseException("Data Base: query is user exists "+user+" failed");
		}
	}


	@SuppressWarnings("unchecked")
	@Override
	public void modifyPost(int postId, PostContent pc) throws DatabaseException {
		try{
			Post temp=null;
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Post> posts=(List<Post>)session.createQuery("from Post where postId="+postId).list();      
			if(!(posts==null ||posts.size()==0 || posts.get(0)==null)){
				temp=posts.get(0);
				temp.setContent(pc);
			}
			session.getTransaction().commit();
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: modifing post "+postId+" failed");
			throw new DatabaseException("Data Base: modifing post "+postId+" failed");
		}

	}


	@SuppressWarnings("unchecked")
	@Override
	public Moderator promoteModerator(String username)
	throws DatabaseException, NoSuchUserException {
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<LoggedInUser> lst=(List<LoggedInUser>)session.createQuery("select user from LoggedInUser user where user.username='"+username+"'").list();
			if(lst==null ||lst.size()==0 || lst.get(0)==null){
				session.getTransaction().commit();
				throw new NoSuchUserException("Data Base: promoting user: "+username+" failed - no such user.");
			}              
			LoggedInUser logged=lst.get(0);
			session.createQuery("delete from LoggedInUser where username='"+username+"'").executeUpdate();
			session.getTransaction().commit();
			session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			Moderator res=new Moderator(logged);
			session.save(res);
			session.getTransaction().commit();
			return res;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: demoting user: "+username+" failed");
			throw new DatabaseException("Data Base: demoting user: "+username+" failed");
		}
	}


	@SuppressWarnings("unchecked")
	@Override
	public Vector<String> getConnectedRegisteredUsers() throws DatabaseException {
		Vector<String> conUsers = new Vector<String>();
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();            
			List<LoggedInUser> lst=(List<LoggedInUser>)session.createQuery("select user from LoggedInUser user where user.connected='connected'").list();
			for(LoggedInUser m:lst)
				conUsers.add(m.getUsername());
			session.getTransaction().commit();
			return conUsers;
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: getting all the connected registered users failed");
			throw new DatabaseException("Data Base: getting all the connected registered users failed");
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void resetDataBase() throws DatabaseException{
		try{
			Guest g = null;
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Guest> guests=(List<Guest>)session.createQuery("from Guest").list();	    
			if(!(guests==null ||guests.size()==0 || guests.get(0)==null)) g=guests.get(0);
			if(g==null) {//if the table is still empty.
				g = new Guest();
				session.save(g);
			}
			else{
				g.setGuestsCount(0);
			}
			//session.createQuery("delete from LoggedInUser").executeUpdate();
			//session.save(new Admin("admin", Security.encryptPassword("1234")));
			//session.createQuery("delete from Post").executeUpdate();
			//session.createQuery("delete from LoggedInUser where UserName='admin1'").executeUpdate();
			session.getTransaction().commit();
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: reset failed");
			throw new DatabaseException("Data Base: reset failed");
		}
//		catch (NoSuchAlgorithmException e) {
//			MyLogger.getInstance().logException("Password encryption error in DM.", e);
//			System.exit(1);
//		}
	}

	public static void main(String[] args){
		try {
			DataManipulatorInt dm = DataManipulatorProxy.getInstance();
			dm.resetDataBase();
			dm.disconnectUser("admin");
		} catch (DatabaseException e) {
			e.printStackTrace();
		}
	}

	@SuppressWarnings("unchecked")
	@Override
	public void connectUser(String username) throws DatabaseException {
		if(!isUserExists(username)){
			MyLogger.getInstance().logSevere("Data Base: connecting user: " + username + " failed user doesn't exists.");
			throw new DatabaseException("Username doesn't exists.");
		}
		try{
			LoggedInUser tUser;
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<LoggedInUser> lst=(List<LoggedInUser>)session.createQuery("select user from LoggedInUser user where user.username='"+username+"'").list();        
			if(!(lst==null || lst.size()==0 || lst.get(0)==null)){
				tUser=lst.get(0);
				tUser.setConnected("connected");
			}
			session.getTransaction().commit();
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: connecting user: " + username + " failed");
			throw new DatabaseException("Data Base: connecting user: " + username + " failed");
		}
	}
	@SuppressWarnings("unchecked")
	@Override
	public void disconnectUser(String username) throws DatabaseException {
		if(!isUserExists(username)){
			MyLogger.getInstance().logSevere("Data Base: disconnecting user: " + username + " failed user doesn't exists.");
			throw new DatabaseException("Username doesn't exists.");
		}
		try{
			LoggedInUser tUser;
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<LoggedInUser> lst=(List<LoggedInUser>)session.createQuery("select user from LoggedInUser user where user.username='"+username+"'").list();        
			if(!(lst==null || lst.size()==0 || lst.get(0)==null)){
				tUser=lst.get(0);
				tUser.setConnected("not connected");
			}
			session.getTransaction().commit();
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: disconnecting user: " + username + " failed");
			throw new DatabaseException("Data Base: disconnecting user: " + username + " failed");
		}
	}
	@SuppressWarnings("unchecked")
	@Override
	public int getGuestCount() throws DatabaseException {
		Guest g = null;
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Guest> guests=(List<Guest>)session.createQuery("from Guest").list();      

			if(!(guests==null ||guests.size()==0 || guests.get(0)==null)) g=guests.get(0);
			session.getTransaction().commit();
			return g.getGuestsCount();
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: get guests count failed");
			throw new DatabaseException("Data Base: get guests count failed");
		}
	}
	@SuppressWarnings("unchecked")
	@Override
	public void incGuestCount() throws DatabaseException {
		Guest g = null;
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Guest> guests=(List<Guest>)session.createQuery("from Guest").list();      
			if(!(guests==null ||guests.size()==0 || guests.get(0)==null)) {
				g = guests.get(0);
				g.incGuestsCount();
			}
			session.getTransaction().commit();
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: inc guests count failed");
			throw new DatabaseException("Data Base: inc guests count failed");
		}

	}
	@SuppressWarnings("unchecked")
	@Override
	public void decGuestCount() throws DatabaseException {
		Guest g = null;
		try{
			Session session = HibernateUtil.getSessionFactory().getCurrentSession();
			session.beginTransaction();
			List<Guest> guests=(List<Guest>)session.createQuery("from Guest").list();      
			if(!(guests==null ||guests.size()==0 || guests.get(0)==null)) {
				g = guests.get(0);
				g.decGuestsCount();
			}
			session.getTransaction().commit();
		}
		catch (HibernateException e) {
			MyLogger.getInstance().logSevere("Data Base: dec guests count failed");
			throw new DatabaseException("Data Base: dec guests count failed");
		}      
	}
}
